[Feat-T3-104] 마이페이지 UI 구현#22
Conversation
## Walkthrough
이번 변경에서는 마이페이지 화면 및 관련 기능을 전면적으로 도입하였습니다. 사용자 데이터 저장소 프로토콜 및 구현체, 에러 타입, 뷰모델, 뷰, 테이블뷰 셀 등이 새로 추가되었으며, DI 컨테이너에 필요한 의존성 등록도 이루어졌습니다. 아이콘 리소스와 공통 유틸리티 확장도 포함됩니다.
## Changes
| 파일/경로 | 변경 요약 |
|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|
| .../DataSourceDependencyAssembler.swift<br>.../PresentationDependencyAssembler.swift | DI 컨테이너에 UserDataRepositoryProtocol 및 MypageViewModel 등록 로직 추가 |
| .../Error/UserError.swift | UserError enum 신설, 사용자 관련 에러 및 한글 설명 제공 |
| .../Repository/UserDataRepository.swift | UserDataRepository 클래스 및 사용자 데이터 로딩 메서드 추가 |
| .../Protocol/Repository/UserDataRepositoryProtocol.swift | UserDataRepositoryProtocol 프로토콜 신설, 토큰/닉네임 로딩 메서드 선언 |
| .../Images.xcassets/chevron_right_icon.imageset/Contents.json<br>.../setting_icon.imageset/Contents.json | chevron_right_icon, setting_icon 이미지셋 리소스 추가 |
| .../DesignSystem/BitnagilIcon.swift | BitnagilIcon에 settingIcon, chevronRightIcon static 프로퍼티 추가 |
| .../Extension/NSObject+.swift | NSObject에 className 인스턴스/타입 프로퍼티 확장 |
| .../TabBarView.swift | DI에서 MypageViewModel 주입, MypageView 파라미터화 및 기존 MypageView 클래스 정의 제거 |
| .../MyPage/View/Component/MypageTableViewCell.swift | MypageTableViewCell 커스텀 셀 클래스 추가, 타이틀/체브론 이미지 포함 |
| .../MyPage/View/MypageView.swift | MypageView 뷰 컨트롤러 신설, UI 구성 및 뷰모델 바인딩, 테이블뷰/이벤트 처리 구현 |
| .../MyPage/ViewModel/MypageViewModel.swift | MypageViewModel 클래스 신설, 메뉴 선택 처리 및 닉네임/외부URL 퍼블리셔 제공 |
## Sequence Diagram(s)
```mermaid
sequenceDiagram
participant User as 사용자
participant MypageView as MypageView
participant MypageViewModel as MypageViewModel
participant UserDataRepo as UserDataRepository
User->>MypageView: 메뉴 선택(예: FAQ)
MypageView->>MypageViewModel: action(input: .didSelectMenu(menu))
MypageViewModel->>MypageViewModel: handleMenuSelection(menu)
alt FAQ/Notice 선택
MypageViewModel->>MypageView: externalURLPublisher로 URL 발행
MypageView->>User: 외부 브라우저로 URL 오픈
else resetGoal 선택
MypageView->>DIContainer: OnboardingViewModel resolve
MypageView->>User: 온보딩 화면으로 이동
end
Note over MypageView: 최초 진입 시
MypageView->>MypageViewModel: 닉네임 요청
MypageViewModel->>UserDataRepo: loadNickname()
UserDataRepo-->>MypageViewModel: 닉네임 반환/에러
MypageViewModel->>MypageView: nickNamePublisher로 닉네임 발행
MypageView->>User: 닉네임 표시Estimated code review effort1 (<30 minutes) Poem
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (11)
Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (2)
13-13: 오타 수정이 필요합니다.
titleLableLeadingSpacing에서titleLabelLeadingSpacing으로 수정해주세요.- static let titleLableLeadingSpacing: CGFloat = 20 + static let titleLabelLeadingSpacing: CGFloat = 20
53-53: Layout 상수명 오타로 인한 불일치위에서 수정된 Layout 상수명과 일치시켜주세요.
- make.leading.equalToSuperview().offset(Layout.titleLableLeadingSpacing) + make.leading.equalToSuperview().offset(Layout.titleLabelLeadingSpacing)Projects/DataSource/Sources/Repository/UserDataRepository.swift (1)
20-27: TODO 주석에 대한 후속 조치가 필요합니다.정적 분석 도구에서도 지적했듯이 accessToken fetch 로직에 대한 TODO가 남아있습니다. 팀 회의를 통해 결정된 후 이 주석을 제거하거나 구현을 업데이트해주세요.
현재 구현 자체는 올바르게 작동하지만, 향후 변경될 가능성이 있는 부분이라는 점을 팀원들과 공유하는 것이 좋겠습니다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (5)
44-46: 불필요한 메서드 오버라이드 제거 필요BaseViewController의 viewDidLoad()에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, super.viewDidLoad() 호출만으로 충분합니다. 현재 오버라이드는 추가 로직이 없어 불필요합니다.
- override func viewDidLoad() { - super.viewDidLoad() - }
60-60: 임시 배경색 처리 개선 제안프로필 이미지뷰의 배경색이 임시로 설정되어 있습니다. 실제 프로필 이미지 로딩 로직이나 기본 플레이스홀더 이미지 처리를 고려해보세요.
147-158: 테이블뷰 셀 구성 로직 개선guard 문에서 실패 시 빈 셀을 반환하는 대신, 적절한 에러 처리나 로깅을 추가하는 것이 좋습니다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell - else { return .init() } + else { + BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell") + return UITableViewCell() + } let title = MypageViewModel.MypageMenu .allCases[indexPath.row] .rawValue cell.configure(title: title) return cell }
44-46: 불필요한 viewDidLoad 오버라이드 제거를 고려해보세요Static analysis에서 지적한 대로, 이 메서드는 super.viewDidLoad()만 호출하고 있어 불필요합니다. Retrieved learning에 따르면 BaseViewController의 viewDidLoad()에서 이미 필요한 메서드들(configureAttribute, configureLayout, bind)을 호출하므로 이 오버라이드는 제거할 수 있습니다.
다음과 같이 메서드를 제거할 수 있습니다:
- override func viewDidLoad() { - super.viewDidLoad() - }
60-60: 임시 배경색에 대한 TODO 주석 추가를 고려해보세요프로필 이미지뷰의 배경색이 임시로 설정되어 있다는 주석이 있습니다. 향후 실제 프로필 이미지 로직이 구현될 때 놓치지 않도록 TODO 주석을 추가하는 것을 권장합니다.
- profileImageView.backgroundColor = BitnagilColor.gray40 // 임시 + profileImageView.backgroundColor = BitnagilColor.gray40 // TODO: 실제 프로필 이미지 로직 구현 필요Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (3)
58-59: resetGoal 메뉴의 처리 로직 명확화 필요현재 resetGoal 메뉴는 빈 break문으로 처리되어 있습니다. View에서 별도로 처리한다는 의도인지 명확하게 주석으로 표시하거나, 일관성을 위해 ViewModel에서 처리하는 것을 고려해보세요.
case .resetGoal: - break + // View에서 직접 네비게이션 처리 + break
58-59: resetGoal 케이스 처리 로직 명확화가 필요합니다현재 resetGoal 케이스는 break만 하고 있어서 실제로는 아무것도 하지 않습니다. 이 케이스는 뷰에서 직접 처리하고 있으니, 주석으로 이를 명확히 해주거나 다른 방식으로 처리하는 것을 고려해보세요.
case .resetGoal: - break + break // 뷰에서 직접 온보딩으로 네비게이션 처리
60-67: 임시 URL을 상수로 관리하는 것을 권장합니다하드코딩된 임시 URL들을 별도 상수로 관리하거나, 향후 실제 API 엔드포인트로 교체할 때를 대비해 설정 가능한 구조로 만드는 것을 고려해보세요.
클래스 상단에 URL 상수들을 추가할 수 있습니다:
final class MypageViewModel: ViewModel { + private enum ExternalURL { + static let notice = "https://www.google.com" // TODO: 실제 공지사항 URL로 변경 + static let faq = "https://www.naver.com" // TODO: 실제 FAQ URL로 변경 + } + enum Input { case didSelectMenu(menu: MypageMenu) }그리고 handleMenuSelection에서:
case .notice: // 임시 url - if let url = URL(string: "https://www.google.com") { + if let url = URL(string: ExternalURL.notice) { externalURLPublisher.send(url) } case .faq: // 임시 url - if let url = URL(string: "https://www.naver.com") { + if let url = URL(string: ExternalURL.faq) { externalURLPublisher.send(url) }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@1x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.pngis excluded by!**/*.png
📒 Files selected for processing (13)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/Common/Error/UserError.swift(1 hunks)Projects/DataSource/Sources/Repository/UserDataRepository.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift(1 hunks)Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(1 hunks)Projects/Presentation/Sources/Common/Extension/NSObject+.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Common/TabBarView.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/MypageView.swift(1 hunks)Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/Common/TabBarView.swift (1)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (2)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (3)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🧬 Code Graph Analysis (5)
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (2)
register(14-16)resolve(18-25)
Projects/Presentation/Sources/Common/TabBarView.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
resolve(18-25)
Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (2)
loadAccessToken(21-27)loadNickname(29-35)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
Projects/Shared/Sources/DIContainer/DIContainer.swift (1)
register(14-16)
Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (1)
Projects/Shared/Sources/Logger/Logger.swift (1)
log(13-22)
🪛 SwiftLint (0.57.0)
Projects/DataSource/Sources/Repository/UserDataRepository.swift
[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)
(todo)
Projects/Presentation/Sources/MyPage/View/MypageView.swift
[Warning] 44-44: Remove overridden functions that don't do anything except call their super
(unneeded_override)
🔇 Additional comments (22)
Projects/Presentation/Sources/Common/Extension/NSObject+.swift (2)
10-18: 간결하고 재사용성 높은 유틸리티 👍
인스턴스/타입 양쪽에서 모두 사용할 수 있게 정의한 점이 유용합니다. 추가 의견 없이 승인드립니다.
10-18: 저장소 내NSObject확장className중복 정의 없음 확인
- 확인 파일: Projects/Presentation/Sources/Common/Extension/NSObject+.swift
- var className: String { … } (11행)
- class var className: String { … } (15행)
- 다른 파일에서 동일 시그니처를 찾을 수 없음
서드파티/사내 모듈(RxSwift, UIKit 등)에 동일 확장 정의가 있는지 별도 검토 부탁드립니다.
Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json (1)
1-23: 표준 애셋 카탈로그 구조로 올바르게 구현됨Xcode의 표준 이미지 애셋 카탈로그 구조를 정확히 따르고 있으며, 1x, 2x, 3x 스케일 변형을 모두 포함하고 있습니다.
Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json (1)
1-23: 표준 애셋 카탈로그 구조로 올바르게 구현됨Xcode의 표준 이미지 애셋 카탈로그 구조를 정확히 따르고 있으며, 모든 스케일 변형을 포함하고 있습니다.
Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift (1)
32-34: 새로운 아이콘이 일관된 패턴으로 추가됨기존 아이콘들과 동일한 번들 해상도 방식과 명명 규칙을 따라 구현되어 있어 일관성이 유지되고 있습니다.
Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift (1)
39-44: ✅ UserDataRepositoryProtocol DI 등록 완료DataSource 레이어의
DataSourceDependencyAssembler.swift에서UserDataRepositoryProtocol이 올바르게 등록되어 있어, Presentation 레이어의MypageViewModel의존성 해결에 문제가 없습니다. 추가 확인이나 조치가 필요 없습니다.
- Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
DIContainer.shared.register(type: UserDataRepositoryProtocol.self) { _ in UserDataRepository(...) }Projects/Presentation/Sources/Common/TabBarView.swift (2)
41-44: 의존성 주입이 올바르게 구현됨MypageViewModel의 의존성 해상도가 프로젝트의 일관된 패턴을 따르고 있으며, 오류 처리도 적절합니다.
49-49: ViewModel 주입으로 MVVM 아키텍처 적용MypageView가 ViewModel을 생성자를 통해 주입받도록 변경되어 적절한 의존성 주입 패턴을 따르고 있습니다.
Projects/DataSource/Sources/Common/Error/UserError.swift (1)
8-24: 에러 처리 구현이 잘 되어 있습니다.
Error와CustomStringConvertible프로토콜을 적절히 구현하고, 각 에러 케이스에 대한 명확한 한국어 메시지를 제공합니다.unknown케이스에서 원본 에러의localizedDescription을 포함하는 것도 디버깅에 도움이 될 것입니다.Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift (1)
41-43: 의존성 주입 등록이 올바르게 구현되었습니다.기존의 다른 Repository 등록 패턴과 일관성 있게 구현되었으며, 필요한 storage 의존성들을 올바르게 주입하고 있습니다.
Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift (1)
8-17: 프로토콜 설계가 깔끔하고 명확합니다.사용자 데이터 로딩에 필요한 메서드들이 잘 정의되어 있으며, 한국어 문서화도 충실합니다.
throws를 사용하여 실패 케이스를 적절히 처리할 수 있도록 설계된 점이 좋습니다.Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift (1)
11-57: 테이블뷰 셀 구현이 잘 되어 있습니다.SnapKit을 활용한 Auto Layout 설정과 Layout enum을 통한 상수 관리, 디자인 시스템 활용 등이 프로젝트의 기존 패턴과 일관성 있게 구현되었습니다.
Projects/DataSource/Sources/Repository/UserDataRepository.swift (1)
11-36: Repository 구현이 프로토콜 명세를 잘 따르고 있습니다.적절한 storage 추상화를 사용하고, 실패 시 명확한 에러를 던지도록 구현되어 있습니다. 의존성 주입을 통한 초기화도 깔끔합니다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (4)
13-25: 레이아웃 상수 구성이 잘 되어있음Layout enum을 통한 상수 관리가 깔끔하게 구성되어 있고, 코드 가독성과 유지보수성을 향상시킵니다.
83-116: SnapKit을 활용한 Auto Layout 구성이 적절함제약 조건 설정이 명확하고 일관성 있게 구성되어 있습니다. safeArea를 적절히 활용하여 다양한 화면 크기에 대응할 수 있도록 구현되었습니다.
12-25: Layout 상수 관리가 잘 구조화되어 있습니다private enum을 사용하여 레이아웃 상수들을 체계적으로 관리하고 있어 유지보수성이 좋습니다. 특히
profileImageViewCornerRadius가profileImageViewSize의 절반으로 계산되는 것도 일관성 있게 처리되어 있습니다.
35-42: 초기화 메서드 구현이 적절합니다DI를 통한 ViewModel 주입과 Combine의 cancellables 초기화가 올바르게 구현되어 있습니다. Storyboard 초기화를 막는 fatalError 처리도 적절합니다.
Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift (5)
41-46: 초기화 시 에러 처리가 적절함사용자 데이터 로딩 실패 시 로그를 남기면서도 앱이 계속 동작하도록 구현된 것이 좋습니다. 닉네임 로딩 실패 시에도 UI가 정상적으로 표시됩니다.
23-27: MypageMenu enum 구성이 깔끔함String 원시값을 가진 CaseIterable enum으로 메뉴 항목을 관리하는 방식이 깔끔하고, UI에서 사용하기에 적합합니다.
37-39: Output 구조체를 통한 퍼블리셔 관리가 적절함AnyPublisher를 사용하여 구현 세부사항을 숨기고, Output 구조체로 퍼블리셔들을 그룹핑한 것이 MVVM 패턴에 잘 맞습니다.
13-27: Input/Output 패턴과 메뉴 구조가 잘 설계되어 있습니다
- Input/Output 패턴으로 뷰모델의 인터페이스가 명확하게 정의되어 있습니다
- MypageMenu enum을 CaseIterable로 구현하여 테이블뷰에서 활용하기 좋게 구조화되어 있습니다
- 한국어로 된 rawValue가 UI에 직접 사용될 수 있어 편리합니다
30-31: Subject 타입 선택이 적절합니다
- 닉네임은 CurrentValueSubject로 초기값과 최신 상태를 유지할 수 있도록 했고
- 외부 URL은 PassthroughSubject로 일회성 이벤트를 처리하도록 한 것이 적절한 선택입니다
| viewModel.output.externalURLPublisher | ||
| .sink { url in | ||
| UIApplication.shared.open(url) | ||
| } | ||
| .store(in: &cancellables) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
외부 URL 열기 시 에러 처리 추가 필요
UIApplication.shared.open(url)이 실패할 가능성을 고려하여 에러 처리를 추가하는 것이 좋습니다.
viewModel.output.externalURLPublisher
.sink { url in
- UIApplication.shared.open(url)
+ UIApplication.shared.open(url) { success in
+ if !success {
+ BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
+ }
+ }
}
.store(in: &cancellables)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| viewModel.output.externalURLPublisher | |
| .sink { url in | |
| UIApplication.shared.open(url) | |
| } | |
| .store(in: &cancellables) | |
| viewModel.output.externalURLPublisher | |
| .sink { url in | |
| - UIApplication.shared.open(url) | |
| + UIApplication.shared.open(url) { success in | |
| + if !success { | |
| + BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)") | |
| + } | |
| + } | |
| } | |
| .store(in: &cancellables) |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 126
to 130, the call to UIApplication.shared.open(url) does not handle potential
failures. Modify the code to use the version of
open(_:options:completionHandler:) that provides a completion handler, and add
error handling logic inside the completion handler to properly manage cases
where opening the URL fails.
🛠️ Refactor suggestion
외부 URL 열기 시 에러 처리 추가를 권장합니다
UIApplication.shared.open(url)이 실패할 수 있는 경우가 있으므로 에러 처리를 추가하는 것이 좋겠습니다.
- viewModel.output.externalURLPublisher
- .sink { url in
- UIApplication.shared.open(url)
- }
- .store(in: &cancellables)
+ viewModel.output.externalURLPublisher
+ .sink { url in
+ UIApplication.shared.open(url) { success in
+ if !success {
+ BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)")
+ }
+ }
+ }
+ .store(in: &cancellables)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| viewModel.output.externalURLPublisher | |
| .sink { url in | |
| UIApplication.shared.open(url) | |
| } | |
| .store(in: &cancellables) | |
| viewModel.output.externalURLPublisher | |
| .sink { url in | |
| UIApplication.shared.open(url) { success in | |
| if !success { | |
| BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)") | |
| } | |
| } | |
| } | |
| .store(in: &cancellables) |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 126
to 130, the call to UIApplication.shared.open(url) does not handle potential
failures. Modify the code to use the version of
open(_:options:completionHandler:) that provides a completion handler, and add
error handling logic inside the completion handler to manage cases where opening
the URL fails.
| guard | ||
| let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell | ||
| else { return .init() } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
테이블뷰 셀 dequeue 실패 시 더 안전한 처리를 권장합니다
현재 빈 UITableViewCell을 반환하고 있지만, 이는 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 셀을 제공하는 것이 좋겠습니다.
guard
let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell
- else { return .init() }
+ else {
+ BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell")
+ return UITableViewCell()
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| guard | |
| let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell | |
| else { return .init() } | |
| guard | |
| let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell | |
| else { | |
| BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell") | |
| return UITableViewCell() | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 148
to 150, the dequeueReusableCell call returns an empty UITableViewCell on
failure, which can cause unexpected UI issues. Instead, implement clearer error
handling by either logging the failure, asserting, or providing a fallback cell
that matches the expected type to ensure UI consistency and easier debugging.
| guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else { | ||
| fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.") | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
fatalError 사용에 대한 검토 필요
의존성이 등록되지 않았을 때 앱이 크래시되는 것보다는, 사용자에게 적절한 에러 메시지를 보여주거나 이전 화면으로 돌아가는 것이 더 나은 UX를 제공할 수 있습니다.
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
- fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
+ BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.")
+ // TODO: 사용자에게 에러 알림 표시
+ return
}🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 170
to 172, replace the fatalError call used when onboardingViewModel dependency is
not resolved with a more user-friendly error handling approach. Instead of
crashing, implement logic to show an appropriate error message to the user or
navigate back to the previous screen to improve UX when the dependency is
missing.
🛠️ Refactor suggestion
의존성 해결 실패 시 더 우아한 에러 처리를 고려해보세요
fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 대안적인 에러 처리나 fallback 로직을 고려해보시기 바랍니다.
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else {
- fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.")
+ BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.")
+ // 적절한 에러 처리나 사용자 알림 로직 추가
+ return
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else { | |
| fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.") | |
| } | |
| guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else { | |
| BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.") | |
| // 적절한 에러 처리나 사용자 알림 로직 추가 | |
| return | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/View/MypageView.swift around lines 170
to 172, replace the fatalError call used when onboardingViewModel dependency
resolution fails with a more graceful error handling approach. Implement
alternative error handling such as returning a default view model, showing an
error message to the user, or safely recovering without crashing the app to
avoid forced termination in production.
| do { | ||
| let nickname = try userDataRepository.loadNickname() | ||
| nicknamePublisher.send(nickname) | ||
| } catch { | ||
| BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)") | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
초기화 시 에러 처리를 개선할 수 있습니다
현재는 닉네임 로드 실패 시 로그만 남기고 빈 문자열을 유지하고 있습니다. 사용자 경험을 위해 기본값이나 대체 텍스트를 제공하는 것을 고려해보세요.
do {
let nickname = try userDataRepository.loadNickname()
nicknamePublisher.send(nickname)
} catch {
BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)")
+ nicknamePublisher.send("사용자") // 기본 닉네임 제공
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| do { | |
| let nickname = try userDataRepository.loadNickname() | |
| nicknamePublisher.send(nickname) | |
| } catch { | |
| BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)") | |
| } | |
| do { | |
| let nickname = try userDataRepository.loadNickname() | |
| nicknamePublisher.send(nickname) | |
| } catch { | |
| BitnagilLogger.log(logType: .debug, message: "\(error.localizedDescription)") | |
| nicknamePublisher.send("사용자") // 기본 닉네임 제공 | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift around
lines 41 to 46, the error handling when loading the nickname only logs the error
but leaves the nickname empty. Improve this by providing a default or fallback
nickname value in the catch block and sending it via nicknamePublisher to ensure
a better user experience.
| case .notice: // 임시 url | ||
| if let url = URL(string: "https://www.google.com") { | ||
| externalURLPublisher.send(url) | ||
| } | ||
| case .faq: // 임시 url | ||
| if let url = URL(string: "https://www.naver.com") { | ||
| externalURLPublisher.send(url) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
하드코딩된 URL을 설정으로 관리 제안
임시 URL들이 하드코딩되어 있습니다. 향후 유지보수를 위해 설정 파일이나 Constants로 관리하는 것을 고려해보세요.
case .notice: // 임시 url
- if let url = URL(string: "https://www.google.com") {
+ if let url = URL(string: AppConfig.noticeURL) {
externalURLPublisher.send(url)
}
case .faq: // 임시 url
- if let url = URL(string: "https://www.naver.com") {
+ if let url = URL(string: AppConfig.faqURL) {
externalURLPublisher.send(url)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| case .notice: // 임시 url | |
| if let url = URL(string: "https://www.google.com") { | |
| externalURLPublisher.send(url) | |
| } | |
| case .faq: // 임시 url | |
| if let url = URL(string: "https://www.naver.com") { | |
| externalURLPublisher.send(url) | |
| } | |
| case .notice: // 임시 url | |
| if let url = URL(string: AppConfig.noticeURL) { | |
| externalURLPublisher.send(url) | |
| } | |
| case .faq: // 임시 url | |
| if let url = URL(string: AppConfig.faqURL) { | |
| externalURLPublisher.send(url) | |
| } |
🤖 Prompt for AI Agents
In Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift around
lines 60 to 67, the URLs for .notice and .faq cases are hardcoded. To improve
maintainability, move these URLs to a configuration file or a Constants struct,
then reference those constants here instead of hardcoding the strings directly.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (3)
126-130: 외부 URL 열기 시 에러 처리 추가를 권장합니다
UIApplication.shared.open(url)이 실패할 수 있는 경우가 있으므로 에러 처리를 추가하는 것이 좋겠습니다.- viewModel.output.externalURLPublisher - .sink { url in - UIApplication.shared.open(url) - } - .store(in: &cancellables) + viewModel.output.externalURLPublisher + .sink { url in + UIApplication.shared.open(url) { success in + if !success { + BitnagilLogger.log(logType: .error, message: "Failed to open URL: \(url)") + } + } + } + .store(in: &cancellables)
148-150: 테이블뷰 셀 dequeue 실패 시 더 안전한 처리를 권장합니다현재 빈 UITableViewCell을 반환하고 있지만, 이는 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 셀을 제공하는 것이 좋겠습니다.
guard let cell = tableView.dequeueReusableCell(withIdentifier: MypageTableViewCell.className) as? MypageTableViewCell - else { return .init() } + else { + BitnagilLogger.log(logType: .error, message: "Failed to dequeue MypageTableViewCell") + return UITableViewCell() + }
170-172: 의존성 해결 실패 시 더 우아한 에러 처리를 고려해보세요fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 대안적인 에러 처리나 fallback 로직을 고려해보시기 바랍니다.
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else { - fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.") + BitnagilLogger.log(logType: .error, message: "onboardingViewModel 의존성이 등록되지 않았습니다.") + // 적절한 에러 처리나 사용자 알림 로직 추가 + return }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@1x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.pngis excluded by!**/*.png
📒 Files selected for processing (13)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/Common/Error/UserError.swift(1 hunks)Projects/DataSource/Sources/Repository/UserDataRepository.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift(1 hunks)Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(1 hunks)Projects/Presentation/Sources/Common/Extension/NSObject+.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Common/TabBarView.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/MypageView.swift(1 hunks)Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json
- Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json
- Projects/Presentation/Sources/Common/Extension/NSObject+.swift
- Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift
- Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
- Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
- Projects/Presentation/Sources/Common/TabBarView.swift
- Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift
- Projects/DataSource/Sources/Common/Error/UserError.swift
- Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift
- Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift
🧰 Additional context used
🧠 Learnings (1)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (5)
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: YAPP-Github/Bitnagil-iOS#19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/MyPage/View/MypageView.swift
[Warning] 44-44: Remove overridden functions that don't do anything except call their super
(unneeded_override)
Projects/DataSource/Sources/Repository/UserDataRepository.swift
[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)
(todo)
🔇 Additional comments (4)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (2)
11-18: 의존성 주입을 통한 깔끔한 구조저장소 프로토콜들을 의존성 주입으로 받아서 테스트 가능하고 유연한 구조를 만들어주셨네요. 잘 구현되었습니다.
29-36: 일관된 에러 처리 패턴loadAccessToken과 동일한 패턴으로 구현되어 일관성이 좋고, 적절한 커스텀 에러를 사용해서 에러 핸들링이 잘 되어있습니다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (2)
48-72: 체계적인 UI 설정 구현디자인 시스템을 잘 활용하고 있고, UI 컴포넌트들이 체계적으로 설정되어 있습니다. 임시로 설정된 프로필 이미지 배경색도 적절히 주석 처리되어 있네요.
74-117: 깔끔한 레이아웃 구성SnapKit을 활용한 Auto Layout 설정이 체계적이고, Layout enum의 상수들을 잘 활용해서 가독성과 유지보수성이 좋습니다.
| // TODO: - accessToken fetch 로직 상의 후 결정 | ||
| func loadAccessToken() throws -> String { | ||
| guard | ||
| let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue) | ||
| else { throw UserError.accessTokenLoadFailed } | ||
|
|
||
| return token | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
TODO 주석 해결 필요
SwiftLint에서 TODO 주석에 대한 경고가 발생하고 있습니다. 현재 구현이 적절하다면 TODO를 제거하거나, 실제로 논의가 필요하다면 구체적인 계획을 세우시는 것이 좋겠습니다.
- // TODO: - accessToken fetch 로직 상의 후 결정
+ // 키체인에서 액세스 토큰을 로드또는 실제로 로직 변경이 필요하다면 이슈를 생성해서 추적하는 것을 고려해보세요.
TODO 주석 정리 필요
SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다.
현재 구현이 확정된 상태라면 해당 주석을 삭제하거나, 실제 의도를 명확히 설명하는 주석으로 변경해주세요.
아직 로직 변경이 논의 중이라면 별도의 이슈를 생성해 추적하는 방안을 권장드립니다.
- 대상 파일: Projects/DataSource/Sources/Repository/UserDataRepository.swift (20–27행)
- 예시 변경안:
- // TODO: - accessToken fetch 로직 상의 후 결정
+ // 키체인에서 액세스 토큰을 로드📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // TODO: - accessToken fetch 로직 상의 후 결정 | |
| func loadAccessToken() throws -> String { | |
| guard | |
| let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue) | |
| else { throw UserError.accessTokenLoadFailed } | |
| return token | |
| } | |
| // 키체인에서 액세스 토큰을 로드 | |
| func loadAccessToken() throws -> String { | |
| guard | |
| let token = keychainStorage.load(forKey: TokenType.accessToken.rawValue) | |
| else { throw UserError.accessTokenLoadFailed } | |
| return token | |
| } |
🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)
(todo)
🤖 Prompt for AI Agents
In Projects/DataSource/Sources/Repository/UserDataRepository.swift around lines
20 to 27, the TODO comment causes SwiftLint warnings. If the accessToken fetch
logic is finalized, remove the TODO comment entirely or replace it with a clear
explanatory comment. If the logic is still under discussion, create a separate
issue to track it and remove the TODO from the code to avoid lint warnings.
choijungp
left a comment
There was a problem hiding this comment.
띵동 매우 감사 ... 웰끔입니다 ......... 🥺
| var className: String { | ||
| return String(describing: type(of: self)) | ||
| } | ||
|
|
||
| class var className: String { | ||
| return String(describing: self) | ||
| } |
There was a problem hiding this comment.
요 className 너무너무 좋은 것 같아요 !!
테이블 뷰 셀 정의할 때 하드코딩 안하고 이렇게 하는 고지능적 개발 !!! 감사합니다.
다만 궁금한게 var className와 class var className가 어떻게 다른지 알 수 있을까요 ??
둘 다 사용해야만 className을 빼올 수 있는건가용 ??
제가 feat/myPage 브랜치에서 다음 부분을 주석처리해도 마이페이지가 잘 작동하는 것 같아 궁금해서 질문 남겨봅니다 !!!
var className: String {
return String(describing: type(of: self))
}추가적으로 이 extension이 public인 이유도 궁금합니다 !!
There was a problem hiding this comment.
우선 public일 필요는 전혀 없습니다!! 추가의 이뉴는 아시다시피 UIView가 NSObject의 서브 클래스이기 때문에 UIView 서브 클래스들의 클래스 면 사용을 편하게 하기 위해서 인데요!! 때문에 Presentation 모듈 밖에서 사용할 일이 없으니 떼는게 좋을거 같아요!! 진짜 꼼꼼하게 봐주셨군요 감사합니다!!!
두 변수의 차이점은 인스턴스 변수냐, 클래스 변수냐의 차이입니다.
var class의 경우, 인스턴스가 존재할 때 해당 인스턴스의 클래스 이름을 불러올 필요가 있을때 사용하시면 되구class var의 경우, 타입 자체의 이름을 String으로 불러올 때 사용하시면 좋을 것 같습니다!
아직은 두 번째 class var 만 사용하고 있지만, 정말 추후 혹시나 인스턴스의 클래스 이름을 String으로 빼오는 경우도 고려해서 한 번에 추가해 두었습니다!
There was a problem hiding this comment.
헐 ~ 바로 이해됩니다 .... 띵천재 띵고수 😎
많이 배워갑니다 ~~
| guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self) else { | ||
| fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.") |
There was a problem hiding this comment.
요 부분에 대해서 혼자 진짜 정말정말 많이 고민했는데 아직 잘 모르겠는 부분이 있습니다 ...........
현재 OnboardingView를 재사용하고 있잖아요 ..
그러면서 필요한 OnboardingViewModel을 DIContainer에서 뽑아 쓰게 됩니다 !!
DIContainer의 구현 세부사항을 확인해보면 딕셔너리로 value에 @escaping 클로저를 저장하여
의존성이 정말 필요할 때 해당 인스턴스를 반환해서 사용하고 있습니다 ...
// DIContainer 일부 ..
private var storage: [String: (DIContainer) -> Any] = [:]
public func resolve<T>(type: T.Type) -> T? {
guard let instance = storage["\(type)"]?(self) as? T else {
BitnagilLogger.log(logType: .error, message: "\(type) Resolve Fail: 등록되지 않은 의존성입니다.")
return nil
}
return instance
}그럼 이미 OnboardingViewModel은 OnboaringView를 처음 생성(ex. 로그인 후 온보딩 진입)할 때 이미 인스턴스가 생성되어 사용되게 되고,
OnboardingView를 재사용할 때(ex. 마이페이지에서 목표 재설정) DIContianer에서 OnboardingViewModel을 꺼내 쓰게 된다면 OnboaringViewModel의 새로운 인스턴스를 생성해서 사용하게 되는 구조가 맞을까요 ??
이건 불가피한 상황인지 .. 어떻게 생각하시나요 ....
이 코멘트로 .... 제 고민이 잘 전달될지 모루겠네요 ㅠㅠ
There was a problem hiding this comment.
생성해야하는 의존성이 굉장히 무겁다면 하나의 인스턴스를 DIContainer에 등록해두고, 같은 인스턴스를 재활용해서 사용하는 방법을 고려해보겠지만, 현재 구조도 괜찮지 않을까요?
항상 새로운 인스턴스를 반환하기 때문에 메모리 상에 독립적으로 존재한다는 점도, 단점이 될 수도 있지만, 장점이 될 수도 있다고 생각합니다! 상태를 공유하지 않기 때문에 예상치 못한 동작을 방지할 수도 있으니까요
조이의 의견도 궁금합니다!!
There was a problem hiding this comment.
음 뷰모델의 경우는 장점이 될 수 있다고 생각하는데 .. 저희 Repository나 UseCase는 따로 상태를 갖고 있는 녀석들은 아닌데 요 부분에서는 약간 단점이 될 수 있을 것 같다는 고민이 됩니다 !!!
하지만 23일 자료에 넣어버릴깝쇼 ??
There was a problem hiding this comment.
좋은 생각입니다!! 일단 빠르게 쳐내는 것이 중요하니까요.. 이 부분에 대해서는 다음주에 길게 이야기 나눠보면 좋을거 같습니다!
- BitnagilIcon에 이미지 추가
- {class이름}.className을 통해 클래스의 이름을 스트링으로 사용할 수 있도록 구현
- 유저 정보 (닉네임, accessToken)을 가져올 수 있는 repository 구현 - UserDataRepository 의존성 등록
- 마이페이지 화면 구현 - 의존성 등록 - TabBar에서 Myapge 클래스 삭제
|
@choijungp 1ae7ba7 에서 리뷰 주신 내용 반영했습니다. 감사합니다! |
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (9)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (3)
20-20: TODO 주석 해결 필요SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 현재 구현이 확정된 상태라면 해당 주석을 삭제하거나, 실제 의도를 명확히 설명하는 주석으로 변경해주세요.
20-20: TODO 주석 해결 필요SwiftLint에서 TODO 주석으로 인한 경고가 계속 발생하고 있습니다. 현재 구현이 확정되었다면 주석을 제거하거나 구체적인 설명으로 변경해주세요.
20-20: TODO 주석 정리가 필요합니다SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 현재 구현이 확정되었다면 해당 주석을 제거하거나 명확한 설명으로 변경해주세요.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (6)
134-137: 셀 dequeue 실패 시 더 안전한 처리 필요현재 빈 UITableViewCell을 반환하고 있지만, 이는 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 로직을 고려해보세요.
157-159: 의존성 해결 실패 시 더 우아한 에러 처리 필요fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 로깅 후 적절한 fallback 처리나 사용자 알림을 고려해보세요.
135-137: 테이블뷰 셀 dequeue 실패 시 더 안전한 처리가 필요합니다현재 빈 UITableViewCell을 반환하고 있어 예상치 못한 UI 동작을 일으킬 수 있습니다. 더 명확한 에러 처리나 적절한 fallback 셀 제공을 고려해주세요.
157-159: 의존성 해결 실패 시 더 우아한 에러 처리를 고려해주세요fatalError는 앱을 강제 종료시키므로 프로덕션 환경에서는 위험할 수 있습니다. 대안적인 에러 처리나 fallback 로직을 고려해보시기 바랍니다.
135-137: 셀 dequeue 실패 시 더 안전한 처리가 필요합니다현재 빈 UITableViewCell을 반환하고 있어 예상치 못한 UI 동작을 일으킬 수 있습니다. 로깅이나 적절한 fallback 처리를 고려해주세요.
157-159: 의존성 해결 실패 시 더 우아한 에러 처리를 권장합니다
fatalError사용은 프로덕션 환경에서 위험할 수 있습니다. 로깅과 함께 더 안전한 에러 처리 방식을 고려해주세요.
🧹 Nitpick comments (3)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (3)
117-117: TODO 주석 정리 권장SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 설정 페이지 구현이 확정된 계획이라면 별도 이슈로 추적하고 TODO를 제거하는 것을 권장합니다.
117-117: TODO 주석 해결이 필요합니다SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 설정 페이지 연결 계획이 있다면 구체적인 일정을 명시하거나, 당분간 구현 예정이 없다면 주석을 제거해주세요.
117-117: TODO 주석 정리를 권장합니다SwiftLint에서 TODO 주석으로 인한 경고가 발생하고 있습니다. 설정 페이지 구현이 계획되어 있다면 별도 이슈로 추적하거나, 명확한 설명 주석으로 변경하는 것을 고려해주세요.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@1x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/chevron_right_icon@3x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@1x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@2x.pngis excluded by!**/*.pngProjects/Presentation/Resources/Images.xcassets/setting_icon.imageset/setting_icon@3x.pngis excluded by!**/*.png
📒 Files selected for processing (13)
Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift(1 hunks)Projects/DataSource/Sources/Common/Error/UserError.swift(1 hunks)Projects/DataSource/Sources/Repository/UserDataRepository.swift(1 hunks)Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift(1 hunks)Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json(1 hunks)Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift(1 hunks)Projects/Presentation/Sources/Common/Extension/NSObject+.swift(1 hunks)Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift(1 hunks)Projects/Presentation/Sources/Common/TabBarView.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift(1 hunks)Projects/Presentation/Sources/MyPage/View/MypageView.swift(1 hunks)Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- Projects/Presentation/Resources/Images.xcassets/chevron_right_icon.imageset/Contents.json
- Projects/DataSource/Sources/Common/DataSourceDependencyAssembler.swift
- Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
- Projects/Presentation/Resources/Images.xcassets/setting_icon.imageset/Contents.json
- Projects/DataSource/Sources/Common/Error/UserError.swift
- Projects/Presentation/Sources/Common/Extension/NSObject+.swift
- Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift
- Projects/Domain/Sources/Protocol/Repository/UserDataRepositoryProtocol.swift
- Projects/Presentation/Sources/Common/TabBarView.swift
- Projects/Presentation/Sources/MyPage/View/Component/MypageTableViewCell.swift
- Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift
🧰 Additional context used
🧠 Learnings (1)
Projects/Presentation/Sources/MyPage/View/MypageView.swift (5)
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Onboarding/View/OnboardingRecommendedRoutineView.swift:57-59
Timestamp: 2025-07-16T09:21:15.038Z
Learning: OnboardingRecommendedRoutineView에서 viewWillAppear에 registerOnboarding 호출하는 것이 적절한 이유: 사용자가 이전 페이지에서 온보딩 선택지를 변경한 후 돌아올 때 새로운 선택지로 다시 등록해야 하기 때문. 홈 뷰에서는 이 뷰로 돌아올 수 없어서 중복 호출 문제가 발생하지 않음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift:0-0
Timestamp: 2025-07-16T10:57:16.559Z
Learning: choijungp는 추후 CustomAlertView를 만들어서 에러 처리를 UI로 해결하도록 수정할 계획을 가지고 있음.
Learnt from: choijungp
PR: #19
File: Projects/Domain/Sources/UseCase/Onboarding/OnboardingUseCase.swift:0-0
Timestamp: 2025-07-16T10:58:30.858Z
Learning: choijungp는 OnboardingUseCase의 convertToDictionary 함수에서 에러 처리 개선을 추후 고민하고 반영할 계획을 가지고 있음.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
Learnt from: choijungp
PR: #19
File: Projects/Presentation/Sources/Login/View/TermsAgreementView.swift:44-46
Timestamp: 2025-07-16T09:09:13.869Z
Learning: BaseViewController의 viewDidLoad() 메서드에서 이미 configureAttribute(), configureLayout(), bind()를 호출하므로, 하위 클래스에서 super.viewDidLoad()를 호출하면 이 메서드들이 자동으로 호출된다. 따라서 하위 클래스에서 추가로 호출할 필요가 없다.
🪛 SwiftLint (0.57.0)
Projects/Presentation/Sources/MyPage/View/MypageView.swift
[Warning] 117-117: TODOs should be resolved (- 추후 설정 페이지 연결)
(todo)
Projects/DataSource/Sources/Repository/UserDataRepository.swift
[Warning] 20-20: TODOs should be resolved (- accessToken fetch 로직 상의 후 결정)
(todo)
🔇 Additional comments (10)
Projects/DataSource/Sources/Repository/UserDataRepository.swift (2)
11-36: 깔끔한 Repository 구현의존성 주입 패턴과 프로토콜 추상화를 잘 활용한 구현입니다. 키체인과 UserDefaults를 적절히 분리하여 사용하고, 일관된 에러 처리를 통해 안정성을 확보했습니다.
11-36: 사용자 데이터 접근 레이어 구현이 적절합니다Repository 패턴을 통한 데이터 접근 로직이 잘 구현되어 있습니다. KeychainStorage와 UserDefaultsStorage를 적절히 활용하고 있으며, 에러 처리도 명확하게 되어 있습니다.
Projects/Presentation/Sources/MyPage/View/MypageView.swift (8)
108-113: SFSafariViewController 사용으로 UX 개선이전 리뷰 피드백을 반영하여
UIApplication.shared.open대신SFSafariViewController를 사용하도록 개선되었습니다. 사용자가 앱을 벗어나지 않고 외부 콘텐츠를 볼 수 있어 더 나은 사용자 경험을 제공합니다.
13-119: 잘 구성된 MVVM 아키텍처BaseViewController를 활용한 깔끔한 구조와 Combine을 통한 반응형 바인딩이 잘 구현되어 있습니다. Layout enum을 통한 상수 관리와 명확한 책임 분리가 돋보입니다.
108-113: SFSafariViewController 사용으로 개선되었습니다이전 리뷰에서 제안된
SFSafariViewController사용으로 변경되어 사용자가 앱을 벗어나지 않고도 웹 페이지를 볼 수 있게 되었습니다. 좋은 개선입니다.
13-40: 뷰 컨트롤러 구조와 초기화가 잘 구성되었습니다BaseViewController를 상속받은 구조가 적절하며, Combine을 활용한 cancellables 관리와 의존성 주입을 통한 뷰모델 전달이 잘 구현되어 있습니다.
14-23: Layout 상수 관리가 체계적입니다Layout enum을 통한 상수 관리가 잘 되어 있어 유지보수성과 가독성이 좋습니다. 특히 profileImageViewCornerRadius를 size의 절반으로 계산한 부분이 적절합니다.
8-8: SafariServices import 추가 - 좋은 개선사항입니다이전 피드백을 반영하여
SFSafariViewController사용을 위한 SafariServices import가 추가되었습니다. 앱 내에서 웹페이지를 보여주는 더 나은 사용자 경험을 제공할 수 있게 되었습니다.
44-45: Navigation 구조 개선 - 좋은 변경사항입니다이전 피드백을 반영하여 커스텀 title label 대신 navigation title을 사용하도록 변경되었습니다. 다른 뷰들과의 일관성이 개선되었고 더 자연스러운 레이아웃을 제공합니다.
108-113: SFSafariViewController 사용으로 개선되었습니다이전 피드백을 반영하여
UIApplication.shared.open대신SFSafariViewController를 사용하도록 변경되었습니다. 사용자가 앱을 벗어나지 않고도 웹 콘텐츠를 볼 수 있어 더 나은 사용자 경험을 제공합니다.
choijungp
left a comment
There was a problem hiding this comment.
띵푸루부 ~~ 👍🏻👍🏻👍🏻 짱 고생하셨습니다 !!!!
🌁 Background
📱 Screenshot
👩💻 Contents
📝 Review Note
📬 Reference
Summary by CodeRabbit
신규 기능
리팩터링/기타